#include <algorithm/analytical_model/algorithm/cpu/VectorConvert.h>
#include <algorithm/analytical_model/algorithm/geometry/Utils.h>
#include <algorithm/analytical_model/io/json/converter/AllConverters.h>
#include <algorithm/analytical_model/io/json/transform/TransformJSON.h>
#include <ui/window/dataVisualizationWindow/SceneRenderer.h>
#include <ui/window/dataVisualizationWindow/SceneRendererAdapter.h>
#include <utils/Chrono.h>

#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/iostreams/stream.hpp>

#include <QQuickWindow>

#include <cmath>
// #include <iostream>

using namespace solar;

SceneRendererAdapter::SceneRendererAdapter(QQuickItem* parent)
    : QQuickItem(parent), renderer_(nullptr)
{
    connect(this, &QQuickItem::windowChanged, this, &SceneRendererAdapter::handleWindowChanged);
    format_.setSamples(4);
    if (auto* window_ptr = window(); window_ptr != nullptr)
    {
        this->parent_size_ = window_ptr->size();
    }
    current_time_ = decltype(current_time_)(std::chrono::hours(8) + std::chrono::minutes(0) +
                                            std::chrono::seconds(0));
}

SceneRendererAdapter::~SceneRendererAdapter() { delete this->renderer_; }

void SceneRendererAdapter::handleWindowChanged(QQuickWindow* window) const
{
    if (window != nullptr)
    {
        window->setFormat(format_);
        connect(window, &QQuickWindow::beforeSynchronizing, this, &SceneRendererAdapter::sync,
                Qt::DirectConnection);
        connect(window, &QQuickWindow::sceneGraphInvalidated, this, &SceneRendererAdapter::cleanup,
                Qt::DirectConnection);
        // Ensure we start with cleared to black. The SceneRendererAdapter's blend mode relies on
        // this.
        window->setColor(Qt::black);
    }
}

void SceneRendererAdapter::sync()
{
    if (renderer_ == nullptr)
    {
        renderer_ = new SceneRenderer();
        if (auto* window_ptr = window(); window_ptr != nullptr)
        {
            window_ptr->setFormat(format_);
        }
        connect(window(), &QQuickWindow::beforeRendering, renderer_, &SceneRenderer::init,
                Qt::DirectConnection);
        connect(window(), &QQuickWindow::beforeRenderPassRecording, renderer_,
                &SceneRenderer::paint, Qt::DirectConnection);
    }
    std::shared_lock update_size_lock(this->parent_size_mutex_, std::defer_lock);
    update_size_lock.lock();
    renderer_->setViewportSize(parent_size_ * window()->devicePixelRatio());
    update_size_lock.unlock();
    renderer_->setWindow(window());
}

void SceneRendererAdapter::cleanup()
{
    delete renderer_;
    renderer_ = nullptr;
}

void SceneRendererAdapter::releaseResources()
{
    window()->scheduleRenderJob(new SceneRendererAdapter::CleanupJob(renderer_),
                                QQuickWindow::BeforeSynchronizingStage);
    renderer_ = nullptr;
}

SceneRendererAdapter::CleanupJob::CleanupJob(SceneRenderer* renderer) : renderer_(renderer) {}

void SceneRendererAdapter::CleanupJob::run()
{
    delete this->renderer_;
    this->renderer_ = nullptr;
}

void SceneRendererAdapter::rotateLookAtCenterHorizontal(float degree)
{
    this->renderer_->rotateLookAtCenterHorizontal(degree);
    if (auto* window_ptr = window(); window_ptr != nullptr)
    {
        window_ptr->update();
    }
}

void SceneRendererAdapter::rotateLookAtCenterVertical(float degree)
{
    this->renderer_->rotateLookAtCenterVertical(degree);
    if (auto* window_ptr = window(); window_ptr != nullptr)
    {
        window_ptr->update();
    }
}

void SceneRendererAdapter::rotateLookAtCenter(float horizontal_degree, float vertical_degree)
{
    this->renderer_->rotateLookAtCenter(horizontal_degree, vertical_degree);
    if (auto* window_ptr = window(); window_ptr != nullptr)
    {
        window_ptr->update();
    }
}

void SceneRendererAdapter::updateScene(AnalyticalPipelineParam* params)
{
    this->renderer_->updateScene(params);
    if (auto* window_ptr = window(); window_ptr != nullptr)
    {
        window_ptr->update();
    }
}

void SceneRendererAdapter::updateFocusCenter()
{
    // this->renderer_->setFocusCenter({0, 180, 0});
    // this->renderer_->updateHeliostats(heliostats_);
    // if (auto* window_ptr = window(); window_ptr != nullptr)
    // {
    //     window_ptr->update();
    // }
}

void SceneRendererAdapter::updateSunRayDirection()
{
    this->renderer_->setSunrayDirection({0.306454, -0.790155, 0.530793});
    if (auto* window_ptr = window(); window_ptr != nullptr)
    {
        window_ptr->update();
    }
}

void SceneRendererAdapter::moveCameraCenter(float delta_x, float delta_y, float delta_z)
{
    this->renderer_->moveCameraCenter(delta_x, delta_y, delta_z);
    if (auto* window_ptr = window(); window_ptr != nullptr)
    {
        window_ptr->update();
    }
}

void SceneRendererAdapter::moveLookAtCenter(float delta_x, float delta_y, float delta_z)
{
    this->renderer_->moveLookAtCenter(delta_x, delta_y, delta_z);
    if (auto* window_ptr = window(); window_ptr != nullptr)
    {
        window_ptr->update();
    }
}

void SceneRendererAdapter::setParentSizeWidth(int new_width)
{
    std::shared_lock update_size_lock(this->parent_size_mutex_, std::defer_lock);
    update_size_lock.lock();
    this->parent_size_.setWidth(new_width);
    update_size_lock.unlock();
}

void SceneRendererAdapter::setParentSizeHeight(int new_height)
{
    std::shared_lock update_size_lock(this->parent_size_mutex_, std::defer_lock);
    update_size_lock.lock();
    this->parent_size_.setHeight(new_height);
    update_size_lock.unlock();
}

void SceneRendererAdapter::increaseTime()
{
    // std::cout << current_time_ << '\n';
    auto incident_angles =
        solar::getSolarIncidentAngles(2023, 7, 1, toHours(current_time_), 120.15305555, 30.2669444);
    auto sunray_dir = -solar::radianToDirection(incident_angles.first, incident_angles.second);
    // std::cout << sunray_dir.toString() << '\n';
    this->renderer_->setSunrayDirection(cpu::toFloat3(sunray_dir));
    this->renderer_->updateSceneForSunray();
    if (auto* window_ptr = window(); window_ptr != nullptr)
    {
        window_ptr->update();
    }
    current_time_ = decltype(current_time_)(current_time_.to_duration() + std::chrono::minutes(10));
    while (sunray_dir.y() > 0 || !std::isfinite(sunray_dir.y()))
    {
        current_time_ =
            decltype(current_time_)(current_time_.to_duration() + std::chrono::minutes(10));
        incident_angles = solar::getSolarIncidentAngles(2023, 7, 1, toHours(current_time_),
                                                        120.15305555, 30.2669444);
        sunray_dir = -solar::radianToDirection(incident_angles.first, incident_angles.second);
    }
}
